#include "DeoLexicon.h"


DeoLexicon::DeoLexicon() {
	hashFunction = state_map.hash_function();
	automat.reserve(AUTOMA_FIRST_RESERVE);
	currentState.pos = 0;
	currentState.str_pos = 0;
}

DeoLexicon::~DeoLexicon() {
}

bool DeoLexicon::compare(const mystate_t &s1, const mystate_t &s2) const {
	typedef _List_iterator<_List_val<transition,allocator<transition>>> iter_transition_t;
	if (s1.size() != s2.size()) 
		return false;
	auto it2 = s2.begin();
	for (auto it1 = s1.begin(); 
		  it1 != s1.end(); 
		  it1++, it2++) {
			  if ((*it1).all_fields != (*it2).all_fields)
				  return false;
	}
	return true;
}

int DeoLexicon::make_state(mystate_t &state)
{
	typedef pair<const int, int> trans_int_t;
    /* make zero state */
	if (state.size() == 0) {
		transition t;
		t.all_fields = 0;
		state.push_back(t);
	}

#ifdef _DEBUG
	if (automat.size() + state.size() >= MAX_AUT_SIZE)
        throw "The automaton going to extend over maximum size";
#endif

    /* check if an identical state is in automat */ 
	size_t seed = 0;
	for (auto it = state.begin(); it!=state.end(); it++)
		hash_combine(seed, (*it).all_fields);
	pair<_List_iterator<_List_val<trans_int_t, allocator<trans_int_t>>>, 
		 _List_iterator<_List_val<trans_int_t, allocator<trans_int_t>>>> range;
	range = state_map.equal_range(seed);
	for (mystate_multimap_t::iterator it=range.first; it!=range.second; ++it) {
		bool result = compare(automat[(*it).second], state);
		if (result) {	
			return (*it).second;
		}
	}

#ifdef PRINT_STATISTICS
	statistics.transitions += state.size();
	for (auto it=state.begin(); it!=state.end(); it++)
		statistics.strings += (*it).b.term;
#endif
	/* put state into automat */
	automat.push_back(state);

    /* put state into state map */
	state_map.insert(make_pair(seed, automat.size()-1));

	/* return location of new object */
	return automat.size()-1;
}

bool DeoLexicon::test_automat(str_iterator_t _begin, str_iterator_t _end)
{
	string s1;
    for (str_iterator_t str_it_loc = _begin;str_it_loc != _end;str_it_loc++) {

		/* skip empty lines */
		if ((*str_it_loc).empty())
			continue;

        if (!check_string((*str_it_loc)))
            return false;
	}
	return true;
}

void DeoLexicon::make_automat(str_iterator_t _begin, str_iterator_t _end)
{
	mystate_t larval_state[MAX_STR_LEN+1];
	unsigned int is_terminal[MAX_STR_LEN+1];
	unsigned char s0[MAX_STR_LEN+1] = "";
    string s1;
    size_t i = 0, p;
    transition new_trans;
#ifdef PRINT_STATISTICS
	t1 = clock();
	int iii=0;
#endif
    
	for (str_iterator_t str_it_loc = _begin;str_it_loc != _end;str_it_loc++) {
		s1 = *str_it_loc;

		/* skip empty lines */
		if (s1.empty())
			continue;

        /* find common prefix */
        for (p=0; s1[p] == s0[p]; p++)
            ;
		
        /* emit states for suffix of previous string */
        while (i > p) {
            new_trans.b.dest = make_state(larval_state[i]);
            new_trans.b.attr = s0[--i];
            new_trans.b.term = is_terminal[i+1];
			larval_state[i].push_back(new_trans);
        }

        /* copy suffix of s1 to s0 */
        while (i < s1.length()) {
            s0[i] = s1[i];
            is_terminal[++i] = 0;
			larval_state[i].clear();
        }
        s0[s1.length()] = '\0';
        is_terminal[s1.length()] = 1;
    }
    while (i > 0) {
        new_trans.b.dest = make_state(larval_state[i]);
		new_trans.b.term = is_terminal[i];
        new_trans.b.attr = s0[--i];
		larval_state[i].push_back(new_trans);
    }
    start_state = make_state(larval_state[0]);

	 /* create a pseudo state pointing to the start state */
	automat[0].front().all_fields = start_state;
}

bool DeoLexicon::check_string(string str)
{
	_List_iterator<_List_val<transition, allocator<transition>>> iter;
	mystate_t state = automat[0];
    unsigned pos = state.front().b.dest;
    unsigned int i;
    unsigned char w;
	for (i = 0; i<str.length(); i++) {
        /* get pointer to new state, get current character*/
		state = automat[pos];
		w = str[i];
#ifdef _DEBUG
		if (pos > automat.size())
            throw "error in automat";
#endif	
		/* find current character in state */
		for (iter = state.begin();; iter++) {
			if (iter==state.end()) 
				return false;

			/* get new position in automat */
			if ((*iter).b.attr == (unsigned)w) {
				pos = (*iter).b.dest;
				break;
			}
		}
    }
	return (bool) ((*iter).b.term);
}

string DeoLexicon::listNextString()
{
	static unsigned char temp_str[MAX_STR_LEN+1];
	stringstream ss;
	int i;
	static bool isReturned = false;
	static bool isIterNotInitialized = true; //initialize iterator at first call

	while (true) {
		// If iterator not initialized get first transition
		if (isIterNotInitialized)
			currentState.iter = automat[currentState.pos].begin();

		isIterNotInitialized = false;

		if (currentState.pos == 0 ||
			currentState.iter == automat[currentState.pos].end()) {
			// Go back to previous state.
			stateLoad();
			continue;
		}
		temp_str[currentState.str_pos] = (unsigned char) (*currentState.iter).b.attr;
		if ((*currentState.iter).b.term) {
			if (!isReturned) { // If not printed already return value.
				ss.clear();
				for (i = 0; i <= currentState.str_pos; i++)
					ss << temp_str[i];
				isReturned = true;
				return ss.str();
			} else {
				isReturned = false;
			}
		}

		// Go to another state
		isIterNotInitialized = true;
		stateSave();
	}
}

void DeoLexicon::stateSave() {
	// Save current transition iterator on stack
	pointerStack.push(currentState.iter);

	// Save current state on stack
	stateStack.push(currentState.pos);

	// Jump to next state
	currentState.pos = (*currentState.iter).b.dest;

	// Increase string size
	currentState.str_pos++;
}

void DeoLexicon::stateLoad() {
	// Restore previous transition iterator
	currentState.iter = pointerStack.top();
	pointerStack.pop();

	// Restore previous state
	currentState.pos = stateStack.top();
	stateStack.pop();

	// Go to next transition
	currentState.iter++; 

	// Reduce to previous value
	currentState.str_pos--;
}

void DeoLexicon::read_automat(const char *fname)
{
	string s, sub;
	aut_file.open(fname, ios::in | ios::binary);
#ifdef _DEBUG
	if (!aut_file.is_open())
        throw "Cannot open input file.";
#endif
	vector<string> allWords;
	while (aut_file.good()) {
		getline(aut_file, s,'\n');
		istringstream iss(s);
		do
		{
			iss >> sub;
			allWords.push_back(sub);
		} while (iss);
	}
	make_automat(allWords.begin(), allWords.end());
}

void DeoLexicon::save_automat(const char *fname)
{
	aut_file.open(fname,ios_base::out | ios_base::binary);
    /*TODO: create a pseudo state pointing to the start state */

	for (auto it1 = automat.begin(); it1!=automat.end(); it1++){
		for (auto it2 = (*it1).begin(); it2!=(*it1).end(); it2++)
			aut_file.write(reinterpret_cast<const char*>(&((*it2).all_fields)),sizeof(unsigned));
	}
	aut_file.close();
}

void DeoLexicon::_printTimeStamp(const char *str) {
	t2 = clock();
		cout << str << ": " << (double) (t2 - t1) / CLOCKS_PER_SEC << endl;
		cout.flush();
	t1 = clock();
}